/*=============================================================================

  Library: CFS

  Copyright (c) German Cancer Research Center,
    Division of Medical and Biological Informatics

  Licensed under the Apache License, Version 2.0 (the "License");
  you may not use this file except in compliance with the License.
  You may obtain a copy of the License at

    http://www.apache.org/licenses/LICENSE-2.0

  Unless required by applicable law or agreed to in writing, software
  distributed under the License is distributed on an "AS IS" BASIS,
  WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
  See the License for the specific language governing permissions and
  limitations under the License.

=============================================================================*/

#include <QUrl>

#include "CFSPlugin_p.h"
#include "CFSPluginArchive_p.h"
#include "CFSPluginException.h"
#include "CFSPluginFrameworkContext_p.h"
#include "CFSPlugins_p.h"
#include "CFSVersionRange_p.h"

#include <stdexcept>
#include <iostream>

//----------------------------------------------------------------------------
void CFSPlugins::checkIllegalState() const
{
  if (!fwCtx)
  {
    throw CFSIllegalStateException("This framework instance is not active");
  }
}

//----------------------------------------------------------------------------
CFSPlugins::CFSPlugins(CFSPluginFrameworkContext* fw)
{
  fwCtx = fw;
  plugins.insert(fw->systemPlugin->getLocation(), fw->systemPlugin);
}

//----------------------------------------------------------------------------
void CFSPlugins::clear()
{
  QWriteLocker lock(&pluginsLock);
  plugins.clear();
  fwCtx = 0;
}

//----------------------------------------------------------------------------
QSharedPointer<CFSPlugin> CFSPlugins::install(const QUrl& location, QIODevice* in)
{
  checkIllegalState();

  QSharedPointer<CFSPlugin> res;
  {
    QMutexLocker lock(&objectLock);

    QHash<QString, QSharedPointer<CFSPlugin> >::const_iterator it = plugins.find(location.toString());
    if (it != plugins.end())
    {
      return it.value();
    }

    // install new plugin
    QSharedPointer<CFSPluginArchive> pa;
    QString localPluginPath;
    try
    {
      if (!in)
      {
        // extract the input stream from the given location


        //          //TODO Support for http proxy authentication
        //          //TODO put in update as well
        //          String auth = fwCtx.props.getProperty("http.proxyAuth");
        //          if (auth != null && !"".equals(auth)) {
        //            if ("http".equals(url.getProtocol()) ||
        //                "https".equals(url.getProtocol())) {
        //              String base64 = Util.base64Encode(auth);
        //              conn.setRequestProperty("Proxy-Authorization",
        //                                      "Basic " + base64);
        //            }
        //          }
        //          // Support for http basic authentication
        //          String basicAuth = fwCtx.props.getProperty("http.basicAuth");
        //          if (basicAuth != null && !"".equals(basicAuth)) {
        //            if ("http".equals(url.getProtocol()) ||
        //                "https".equals(url.getProtocol())) {
        //              String base64 = Util.base64Encode(basicAuth);
        //              conn.setRequestProperty("Authorization",
        //                                      "Basic " +base64);
        //            }
        //          }

        if (location.scheme() != "file")
        {
          throw CFSRuntimeException(QString("Unsupported url scheme: ") + location.scheme());
        }
        else
        {
          localPluginPath = location.toLocalFile();
        }
      }
      else
      {
        //TODO copy the QIODevice to a local cache
      }

      pa = fwCtx->storage->insertPlugin(location, localPluginPath);

      res = QSharedPointer<CFSPlugin>(new CFSPlugin());
      res->init(res, fwCtx, pa);
      plugins.insert(location.toString(), res);
    }
    catch (const CFSException& e)
    {
      if (!pa.isNull())
      {
        pa->purge();
      }
      //      if (dynamic_cast<const SecurityException&>(e)) {
      //        throw;
      //      }
      //      else
      //      {
      throw CFSPluginException("Failed to install plugin",
                               CFSPluginException::UNSPECIFIED, e);
      //      }
    }
    catch (...)
    {
      throw CFSPluginException("Failed to install plugin", CFSPluginException::UNSPECIFIED);
    }
  }

  fwCtx->listeners.emitPluginChanged(CFSPluginEvent(CFSPluginEvent::INSTALLED, res));
  return res;
}

//----------------------------------------------------------------------------
void CFSPlugins::remove(const QUrl& location)
{
  QWriteLocker lock(&pluginsLock);
  plugins.remove(location.toString());
}

//----------------------------------------------------------------------------
QSharedPointer<CFSPlugin> CFSPlugins::getPlugin(int id) const
{
  checkIllegalState();

  {
    QReadLocker lock(&pluginsLock);

    QHashIterator<QString, QSharedPointer<CFSPlugin> > it(plugins);
    while (it.hasNext())
    {
      QSharedPointer<CFSPlugin> plugin = it.next().value();
      if (plugin->getPluginId() == id)
      {
        return plugin;
      }
    }
  }
  return QSharedPointer<CFSPlugin>();
}

//----------------------------------------------------------------------------
QSharedPointer<CFSPlugin> CFSPlugins::getPlugin(const QString& location) const
{
  checkIllegalState();

  QReadLocker lock(&pluginsLock);
  QHash<QString, QSharedPointer<CFSPlugin> >::const_iterator it = plugins.find(location);
  if (it != plugins.end()) return it.value();
  return QSharedPointer<CFSPlugin>(0);
}

//----------------------------------------------------------------------------
QSharedPointer<CFSPlugin> CFSPlugins::getPlugin(const QString& name, const CFSVersion& version) const
{
  checkIllegalState();

  {
    QReadLocker lock(&pluginsLock);

    QHashIterator<QString, QSharedPointer<CFSPlugin> > it(plugins);
    while (it.hasNext())
    {
      QSharedPointer<CFSPlugin> plugin = it.next().value();
      if ((name == plugin->getSymbolicName()) && (version == plugin->getVersion()))
      {
        return plugin;
      }
    }
  }
  return QSharedPointer<CFSPlugin>(0);
}

//----------------------------------------------------------------------------
QList<QSharedPointer<CFSPlugin> > CFSPlugins::getPlugins() const
{
  checkIllegalState();

  {
    QReadLocker lock(&pluginsLock);
    return plugins.values();
  }
}

//----------------------------------------------------------------------------
QList<CFSPlugin*> CFSPlugins::getPlugins(const QString& name) const
{
  QList<CFSPlugin*> res;

  {
    QReadLocker lock(&pluginsLock);
    QHashIterator<QString, QSharedPointer<CFSPlugin> > it(plugins);
    while (it.hasNext())
    {
      CFSPlugin* plugin = it.next().value().data();
      if (name == plugin->getSymbolicName())
      {
        res.push_back(plugin);
      }
    }
  }

  return res;
}

//----------------------------------------------------------------------------
QList<CFSPlugin*> CFSPlugins::getPlugins(const QString& name, const CFSVersionRange& range) const
{
  checkIllegalState();

  QList<CFSPlugin*> pluginsWithName = getPlugins(name);
  QList<CFSPlugin*> res;

  QListIterator<CFSPlugin*> it(pluginsWithName);
  while (it.hasNext()) {
    CFSPlugin* plugin = it.next();
    if (range.withinRange(plugin->getVersion()))
    {
      int j = res.size();
      while (--j >= 0)
      {
        if (plugin->getVersion().compare(res.at(j)->getVersion()) <= 0)
        {
          break;
        }
      }
      res.insert(j + 1, plugin);
    }
  }

  return res;
}

//----------------------------------------------------------------------------
QList<QSharedPointer<CFSPlugin> > CFSPlugins::getActivePlugins() const
{
  checkIllegalState();

  QList<QSharedPointer<CFSPlugin> > slist;
  {
    QReadLocker lock(&pluginsLock);
    QHashIterator<QString, QSharedPointer<CFSPlugin> > it(plugins);
    while (it.hasNext())
    {
      QSharedPointer<CFSPlugin> plugin = it.next().value();
      CFSPlugin::State s = plugin->getState();
      if (s == CFSPlugin::ACTIVE || s == CFSPlugin::STARTING) {
        slist.push_back(plugin);
      }
    }
  }
  return slist;
}

//----------------------------------------------------------------------------
void CFSPlugins::load()
{
  QList<QSharedPointer<CFSPluginArchive> > pas = fwCtx->storage->getAllPluginArchives();
  QListIterator<QSharedPointer<CFSPluginArchive> > it(pas);

  {
    QMutexLocker lock(&objectLock);
    while (it.hasNext())
    {
      QSharedPointer<CFSPluginArchive> pa = it.next();
      try
      {
        QSharedPointer<CFSPlugin> plugin(new CFSPlugin());
        plugin->init(plugin, fwCtx, pa);
        plugins.insert(pa->getPluginLocation().toString(), plugin);
      }
      catch (const std::exception& e)
      {
        pa->setAutostartSetting(-1); // Do not start on launch
        pa->setStartLevel(-2); // Mark as uninstalled

        std::cerr << "Error: Failed to load bundle "
            << pa->getPluginId()
            << " ("  << qPrintable(pa->getPluginLocation().toString())  << ")"
            << " uninstalled it!\n";
        std::cerr << e.what() << std::endl;
      }
    }
  }
}

//----------------------------------------------------------------------------
void CFSPlugins::startPlugins(const QList<CFSPlugin*>& slist) const
{
  // Sort in start order
  // Resolve first to avoid dead lock
  QListIterator<CFSPlugin*> it(slist);
  while (it.hasNext())
  {
    CFSPlugin* plugin = it.next();
    CFSPluginPrivate* pp = plugin->d_func();
    pp->getUpdatedState();
  }

  it.toFront();
  while (it.hasNext())
  {
    CFSPlugin* plugin = it.next();
    CFSPluginPrivate* pp = plugin->d_func();
    if (pp->getUpdatedState() == CFSPlugin::RESOLVED)
    {
      try
      {
        plugin->start(0);
      }
      catch (const CFSPluginException& pe)
      {
        pp->fwCtx->listeners.frameworkError(pp->q_func(), pe);
      }
    }
  }
}
